package com.jyw.business.obstacles.handler;

import cn.hutool.core.lang.UUID;
import com.alibaba.fastjson2.JSONObject;
import com.jyw.business.obstacles.domain.ObsPhoneBox;
import com.jyw.business.obstacles.domain.dto.web.UdpProtocolDto;
import com.jyw.business.obstacles.domain.vo.web.LyltConfigVO;
import com.jyw.business.obstacles.service.IObsPhoneBoxService;
import com.jyw.business.obstacles.utils.DbUtil;
import com.jyw.business.obstacles.utils.PhoneUtils;
import com.jyw.common.constant.ConfigKeyConstant;
import com.jyw.common.core.redis.RedisCache;
import com.jyw.system.service.ISysConfigService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * UDP协议解析
 * @author sunshine
 * @date 2022年7月22日
 */
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Slf4j
public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {

	@Autowired
	ThreadPoolTaskExecutor threadPoolTaskExecutor;

	@Autowired
	private IObsPhoneBoxService phoneBoxService;

	@Autowired
	private RedisCache redisCache;

	@Resource
	private ISysConfigService configService;

    @Resource
    private PhoneUtils phoneUtils;


	/**力盈力泰通道前缀*/
	private static final String LYLT_CHANNEL_PREFIX="LYLT:CHANNEL:";

	private Connection connection;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
    	DatagramPacket msg = packet.copy();
    	//异步处理数据
		threadPoolTaskExecutor.execute(()->{
    		protocolParsing(msg);
    	});
    }

    /**
         * 解析数据包协议
     * @param msg void
     * @author sunshine
     * @date 2022年7月22日
     */
    private void protocolParsing(DatagramPacket msg) {
    	try {
        	UdpProtocolDto proto = new UdpProtocolDto();
	    	ByteBuf buffer = msg.content();
	    	String head = ByteBufUtil.hexDump(buffer.readBytes(4));
	    	proto.setHead(head);
	    	int type = (int)buffer.readByte();
	    	proto.setType(type);
	    	int attrLengh = (int)buffer.readByte();
	    	proto.setAttrLengh(attrLengh);
	    	if(attrLengh > 0) {
	        	String attrMsg = buffer.readBytes(attrLengh).toString(StandardCharsets.UTF_8);
	        	proto.setAttrMsg(attrMsg.replaceAll("[\\u0000]",""));
	    	}
	    	buffer.readerIndex(43);
	    	int channelNum = (int)buffer.readByte();
	    	proto.setChannelNum(channelNum);
	    	String extraMsg = buffer.readBytes(20).toString(StandardCharsets.UTF_8);
	    	proto.setExtraMsg(extraMsg.replaceAll("[\\u0000]",""));
	    	log.info("@力盈力泰UDP:收到通道{}的消息:({})",proto.getChannelNum(), JSONObject.toJSONString(proto));
	    	//处理业务数据
	    	if(StringUtils.isNotEmpty(proto.getHead())) {
	        	processData(proto);
	    	}
    	}catch(Exception e) {
    		log.error("@力盈力泰UDP:协议解析异常",e);
    	}
    }

    /**
         * 处理业务逻辑
     * @param proto void
     * @author sunshine
     * @date 2022年7月22日
     */
    private void processData(UdpProtocolDto proto) {
    	//消息类型
    	Integer type = proto.getType();
    	//业务流水Key
		String key = LYLT_CHANNEL_PREFIX + proto.getChannelNum();
		//业务流水
		String guid = redisCache.getCacheObject(key);
		//工作电话
		String currentPhone = proto.getExtraMsg();
		if(StringUtils.isEmpty(currentPhone)) {
			currentPhone = String.valueOf(proto.getChannelNum());
		}
    	//1.表示来电号码
    	if(type.intValue() == 1) {
    		log.info("@力盈力泰UDP:收到通道{}的来电({})",proto.getChannelNum(),proto.getAttrMsg());
    		//处理来电号码前缀
    		proto.setAttrMsg(phoneUtils.filterIncomingPrefix(proto.getAttrMsg()));
    		//防止UDP丢包导致的后续电话无法接受问题
    		String phone = "";
    		if(StringUtils.isNotEmpty(guid)) {
    			phone = redisCache.getCacheObject(key + guid);
    		}
    		if(StringUtils.isEmpty(guid) || !proto.getAttrMsg().equals(phone)) {
    			// 创建工单
    			Date startTime = new Date();
    			guid = "lylt-"+ UUID.fastUUID().toString(true);
				// 加入缓存
				redisCache.setCacheObject(key,guid, 15, TimeUnit.MINUTES);
                redisCache.setCacheObject(key + guid,proto.getAttrMsg(), 10,TimeUnit.MINUTES);
    			phoneBoxService.startWork(proto.getAttrMsg(),guid,currentPhone,startTime);
    		}else {
    			log.info("@力盈力泰UDP忽略1:收到通道{}的重复来电({})",proto.getChannelNum(),proto.getAttrMsg());
    		}
    	}else
    	//2. 表示挂机信息
        if(type.intValue() == 2) {
        	log.info("@力盈力泰UDP:收到通道{}的挂机信息)",proto.getChannelNum());
        	redisCache.deleteObject(key);
        	if(StringUtils.isNotEmpty(guid)) {
        		ObsPhoneBox phoneBox = phoneBoxService.getByOtheraccept(guid);
    			if(phoneBox != null) {
                    phoneBox.setOverTime(new Date());
    				if(StringUtils.isNotEmpty(phoneBox.getCallAccept())) {
    					String yuyingId = phoneBox.getCallAccept();
    					String urlSuffix = getUrlSuffix(yuyingId);
    					if(StringUtils.isNotEmpty(urlSuffix)) {
							LyltConfigVO boxConfig = getBoxConfig();
							String fileUrl = boxConfig.getFile() + urlSuffix.replace("\\", "/");
                            phoneBox.setRecordingFileName(fileUrl);
        					log.info("@力盈力泰UDP:接通挂机添加录音下载文件地址  {}",phoneBox.getRecordingFileName());
    					}else {
    						log.info("@力盈力泰UDP异常2:未从sqlserver查询到语音存储路径:SeqID={},urlSuffix={}",yuyingId,urlSuffix);
    					}
                        phoneBoxService.updateById(phoneBox);
						phoneBoxService.saveLyltVoice(phoneBox);
    				}else {
    					log.info("@力盈力泰UDP异常2:未存储语音数据");
    				}
					//不为空那么推送挂机信息
					phoneBoxService.sendHangUp(phoneBox);
    			}else {
    				log.info("@力盈力泰UDP异常2:已接通但未创建来电记录,丢失数据啦!");
    			}
        	}else {
        		log.info("@力盈力泰UDP忽略:无来电情况下拿起了听筒然后挂了电话");
        	}
        	//根据序列号拉取录音数据

    	}else
    	//3.表示摘机信息
    	if(type.intValue() == 3) {
    		log.info("@力盈力泰UDP:收到通道{}的摘机信息)",proto.getChannelNum());
    		if(StringUtils.isNotEmpty(guid)) {
                ObsPhoneBox phoneBox = phoneBoxService.getByOtheraccept(guid);
    			if(phoneBox != null && !"1".equals(phoneBox.getStatus())) {
                    phoneBox.setOtherStr(currentPhone);//接通号码
                    phoneBox.setStatus("1");//已接
                    phoneBox.setAnsweredTime(new Date());
                    phoneBoxService.updateById(phoneBox);
    				log.info("接通保存： {}, cphone:{},answeredTime:{},overTime:{}",guid,currentPhone,phoneBox.getAnsweredTime(),phoneBox.getOverTime());
					phoneBoxService.sendAnswer(phoneBox);
    			}else {
    				log.info("@力盈力泰UDP异常3:已接通但未创建来电记录,丢失数据啦!");
    			}
			}else {
				log.info("@力盈力泰UDP忽略3:无来电情况下拿起了听筒");
			}
    	}else
    	 //4.表示振铃消失（振了一段时间后，没接，后来振铃消失了）
    	 if(type.intValue() == 4) {
    		log.info("@力盈力泰UDP:收到通道{}的振铃消失)",proto.getChannelNum());
			redisCache.deleteObject(key);
    	}else if(type.intValue()==5){
			 log.info("@力盈力泰UDP:收到通道{}的来电振铃)",proto.getChannelNum());
		 }else if(type.intValue()==8){
			 log.info("@力盈力泰UDP:收到通道{}的外拨号码,呼叫电话:{})",proto.getChannelNum(),proto.getAttrMsg());
			 if(StringUtils.isEmpty(guid)) {
				 // 创建拨出
				 guid = "lylt-"+UUID.fastUUID().toString(true);
				 // 加入缓存
                 redisCache.setCacheObject(key,guid, 15,TimeUnit.MINUTES);
			 }
			 if (!StringUtils.isEmpty(proto.getAttrMsg())) {
                 redisCache.setCacheObject(key + guid,proto.getAttrMsg(), 60 * 10,TimeUnit.MINUTES);
			 }
		 }
		 else
    	//100.表示力盈力泰电话录音软件发来的呼叫序号,是当前该通道正在录音的录音记录的唯一标识。用该标识可以在录音软件中查询到该录音记录
    	if(type.intValue() == 100) {
    		log.info("@力盈力泰UDP:收到通道{}的电话录音软件发来的呼叫序号)",proto.getChannelNum());
    		//存储拉取语音数据的序列号
    		String attrMsg = proto.getAttrMsg();
    		if(StringUtils.isNotEmpty(guid) && StringUtils.isNotEmpty(attrMsg)) {
    			ObsPhoneBox phoneBox = phoneBoxService.getByOtheraccept(guid);
    			if(phoneBox != null) {
    				//用于挂机后拉取录音数据的序列号
                    phoneBox.setCallAccept(attrMsg);
                    phoneBoxService.updateById(phoneBox);
    				log.info("保存录音：序列号 {},流水号:{},工作电话:{}",attrMsg,guid,currentPhone);
    			}else {
    				log.info("@力盈力泰UDP异常100:已接通但未创建来电记录,丢失数据啦!");
    			}
    		}else{
    			log.info("@力盈力泰UDP忽略100:无来电情况下拿起了听筒系统记录了无效的录音数据({})",attrMsg);
			}

			if (!StringUtils.isEmpty(attrMsg)) {
				if(StringUtils.isEmpty(guid)){
					guid = "lylt-"+UUID.fastUUID().toString(true);
					// 加入缓存
					redisCache.setCacheObject(key,guid, 15,TimeUnit.MINUTES);
				}
			}
		}
    }

    /**
         * 获取语音存储位置
     * @param yuyingId 语音序列号
     * @return String 返回存储位置
     * @author sunshine
     * @date 2022年7月22日
     */
    private String getUrlSuffix(String yuyingId) {
    	String returnUrl = null;
    	int times = 0;
    	try {
			LyltConfigVO boxConfig = getBoxConfig();
			if(connection == null || connection.isClosed()) {
    			connection = DbUtil.getConnection(boxConfig.getDatasource(),boxConfig.getUsername(),boxConfig.getPassword());
    		}
    		if(connection != null && StringUtils.isNotEmpty(yuyingId)) {
    			while(returnUrl == null && times++ < 10) {
        			List<String[]> list = DbUtil.getDataBySQL("select top 1 WaveFilePath from CallLogTable WHERE SeqID ='" + yuyingId + "'", connection);
        			if(CollectionUtils.isNotEmpty(list) && list.get(0).length > 0) {
        				returnUrl = list.get(0)[0];
        			}
        			if(returnUrl == null) {
        				Thread.sleep(1000);
        			}
        			log.error("@连接力盈力泰数据库参连接第{}次获取到:WaveFilePath={}",times,returnUrl);
    			}
    		}else {
    			log.error("@连接力盈力泰数据库查询异常:connection == null?{},SeqID={}",connection== null,yuyingId);
    		}
		} catch (Exception e) {
			log.error("@连接力盈力泰数据库异常:",e);
		}
    	return returnUrl;
    }

	/**
	 * 获取盒子配置
	 * @return
	 */
	public LyltConfigVO getBoxConfig(){
		return JSONObject.parse(configService.selectConfigByKey(ConfigKeyConstant.LYLT_BOX_CONFIG)).toJavaObject(LyltConfigVO.class);
	}
}
